<?php
	/**
	 * <version greek="beta" major="1" minor="0">Cochin Splash Hen</version>
	 * <author email="njones@art-k-tec.com">Nika Jones</author>
	 * <copyright start="2007">Art-k-tec Systems, LLC</copyright>
	 * <license type="Apache2.0" href="http://www.opensource.org/licenses/apache2.0.php">Apache License, Version 2.0</license>
	 */

	/**
	* <summary>All of the exception constants for this class.</summary>
	*/
	class bXmlException extends Exception {
		const INVALID_ARRAY						=10002;
		const INVALID_CONVERSION 				=10001;
		const INVALID_CONVERSION_ARRAY 			=10000;
		const INVALID_CONVERSION_STRING 		=10000;
		const INVALID_STRING_XML		 		=10000;
		const INVALID_PERMISSIONS				=10000;
		const INVALID_CONSTRUCTOR				=10000;
		const INVALID_OBJECTS					=10000;
		const INVALID_NODE_NAME					=10000;
		const INVALID_TRANSFORMATION			=10000;
		
		const NOT_bXML							=10000;
		const NOT_LOADED						=10000;
	}

	/**
	 * <summary>
	 * 	Basically a type that covers both bXml and bSimpleXml so you
	 *  can check for an instance of IbXml instead of bother bXml and bSimpleXml
	 * </summary>
	 */
	interface IbXml {}

	class bPathFinder {
		
		private $sPath;
		
		public function __construct($sPath =null) {
			
			$this->sPath =$sPath;
		}
		
		public function __toString() {
			
			return $this->sPath;
		}
	}

	 /**
	 * <summary>
	 * 	Takes the simplexml core and extends a bit
	 * </summary>
	 */
	class bSimpleXml extends SimpleXmlElement implements IbXml{
		private static $nMajor =1;
		private static $nMinor =0;
		private static $sRevision ="0000";
		private static $sDefaultNamespace ="_d";
		
		public static function version() { return self::$nMajor.'.'.self::$nMinor.'.'.self::$sRevision; }
	
		public function _ObjectReference($oBXml =null) {
			
			// keeping the reference of the bXml object inside of the bSimpleXml object. Should not be used
			// publically.
			static $_oBXml =null;
			
			if(isset($oBXml)) {
				
				$_oBXml =$oBXml;
			}
				
			return $_oBXml;
		}

		/// <summary>
		///   Gets the namespaces of the current document/node, and any passed in node,
		///   and adds to the current document/node
		/// </summary>
		/// <returns type="array">
		///   All of the namespace uris and prefixes within the document/node
		/// </returns>
		private function ObjectNamespace($oNode =null) {
			static $aNameSpace =array();
			static $n =0;

			$bFullNode = ($oNode ==null);
			$oNode =($oNode ==null ? $this : $oNode);
			$aDocNameSpace =$oNode->getNamespaces($bFullNode);

			$a =array_diff($aDocNameSpace, array_flip($aNameSpace));

			foreach($a as $sPrefix=>$sNamespace) {

				if($sPrefix == '') { 
					$ns =self::$sDefaultNamespace;
					if(!in_array($sNamespace, array_values($this->_ObjectReference()->GetRegisteredNamespaces()))) {
					
						trigger_error('XPATH 1.0 is unable to handle default namespaces. Use "'.self::$sDefaultNamespace.'" to work with a default namespace.');
					}
				} else {

					$ns =$sPrefix;//'ns'.$n++;
				}
				$this->registerXPathNamespace($ns, $sNamespace);
				$aNameSpace[$sNamespace] =$ns;

			}

			return $aNameSpace;
		}

		public function ObjectType($sType =null) {
			static $sObjectType ='bXml';
			
			if($sType !=null) {
				
				$sObjectType =$sType;
			}
			
			return $sObjectType;
		}

		/// <summary>
		/// 	Holds the single XSLT processor for efficacy reasons
		/// </summary>
		/// <returns type="XsltProcessor" />
		private function ObjectProcessor() {
			static $oProcessor =null;

			if($oProcessor ==null) {

				// Current behavior is that imported stylesheets will override oneanother
				// so we can just fire up one instance of this and keep using.
				$oProcessor =new XsltProcessor();
			}

			return $oProcessor;
		}

		/// <summary>
		/// 	Handles the loading of the stylesheet to the processor. Mantains only one processor
		/// 	for all bXml objects (it's static)
		/// </summary>
		/// <param type="bXml" name="xXsl" byreference="true">The XSLT stylesheetL</param>
		/// <param type="array" name="aArgument" optional="true">The argument data to add to the stylesheet</param>
		private function LoadStylesheet(IbXml $xXsl, array $aArgument =null) {
			$aPathFinderParam =array();
			
			if(is_array($aArgument)) {
				foreach($aArgument as $aNamespaceKeyValue) {
					list($sNamespace, $sKey, $mValue) = $aNamespaceKeyValue;
					if($mValue instanceof bPathFinder) {
						
						$aPathFinderParam[$sKey] =(string)$mValue; // no namespace support... PHP5.2+ doesn't either :p
					} else {
					
						$this->ObjectProcessor()->setParameter($sNamespace, $sKey, $mValue);
					}
 					
				}
			}
			
			foreach($aPathFinderParam as $sParamName => $sSelect) {
			
				//$sXslParam ="<xsl:param name='$sParamName' select='$sSelect' />";
				$xChild =$xXsl->AddChild('xsl:param', null, 'http://www.w3.org/1999/XSL/Transform')
					->AddAttribute('name', $sParamName)
					->AddAttribute('select', $sSelect);
				$xXsl->FirstChild()->NextSibling()->InsertBefore($xChild);
			}
			
			$this->ObjectProcessor()->importStylesheet($xXsl->AsDom());
			unset($xXsl);
		}
		
		/// <summary>
		/// 	Overwrites the AddChild to allow things like adding string XML into the child location
		/// </summary>
		/// <param type="mixed" name="mName">Can be the name of the child node or a full XML Node</param>
		/// <param type="mixed" name="mData">Can be *any* type of Xml (including Stream) or string to be added</param>
		/// <param type="mixed" name="sName">The string namespace URI</param>
		public function AddChild($mName, $mData =null, $sNamespace =null) {
			
			if(is_string($mName)) {
				$sNullNamespace ='http://art-k-tec.com/isNull';
				// sniff to see if $mName is really Xml
				if(!preg_match('/[\/<]/', $mName)) { //is valid
					
					if($mData ==null || $mData =='') { 

						$xChild =parent::AddChild($mName, null, $sNamespace);

						echo $xChild;	
					} else {
						$xChild =parent::AddChild($mName, $mData, $sNamespace);
					}
				}
			}

			if(preg_match('/[\/<]/', $mName) || is_object($mName)) {

				$xNode =bXml::ToXml($mName);
				$oNodeChild =dom_import_simplexml($xNode);

				$oNode =dom_import_simplexml($this);
				$oDom =$oNode->ownerDocument;

				$oChild =$oDom->importNode($oNodeChild, true);
				$oNode->appendChild($oChild);
				
				$xChild =simplexml_import_dom($oChild, get_class($this));
				
			}

			return $xChild;
		}
		
		public function AddAttribute() {

			$a =func_get_args();
			call_user_func_array(array('parent', 'AddAttribute'), $a);

			return $this;
		}
		
		/// <summary>
		/// 	Allows the addition of a CDATA node
		/// </summary>
		/// <param type="string" name="sData">Any string</param>
		/// <returns type="this" />
		public function AddCData($sData) {

			$oNode =dom_import_simplexml($this);
			$oDom =$oNode->ownerDocument;

			$oCData =$oDom->createCDATASection($sData);
			$oNode->appendChild($oCData);

			return $this;
		}


		/// <summary>
		/// 	Allows the addition of a Comment node
		/// </summary>
		/// <param type="string" name="sData">Any string data</param>
		/// <returns type="this" />
		public function AddComment($sData) {

				$oNode =dom_import_simplexml($this);
				$oDom =$oNode->ownerDocument;

				$oComment =$oDom->createComment($sData);
				$oNode->appendChild($oComment);

				return $this;
		}

		/// <summary>
		///  returns a new object based off of the current object
		/// </summary>
		/// <returns type="bXml" />
		public function CloneNode() {

			return new bXml($this->asXml()); // the ->asXML prevents cloning parent and ancestor node
		}

		/// <summary>
		///  Inserts an XML type before a child node
		/// </summary>
		/// <param type="mixed" name="mNode">
		///  The node of any xml type or stream to add to the current node
		/// </param>
		/// <param type="bXml|bSimpleXml" name="xReferenceNode" optional="true">
		///  The child node to insert the node before. If this is not passed in
		///  then the <param name="mNode" /> param will be inserted before the
		///  CURRENT node.
		/// </param>
		/// <returns type="bSimpleXml" />
		public function InsertBefore($mNode, IbXml $xReferenceNode =null) {

			$xThisNode =($xReferenceNode ==null ? $this : $xReferenceNode);

			$oNode =dom_import_simplexml($xThisNode);
			$oDom =$oNode->ownerDocument;

			$xNode =bXml::ToXml($mNode);
			$oInsert =dom_import_simplexml($xNode);

			$oInsertBefore =$oDom->importNode($oInsert, true);
			$oNode->parentNode->insertBefore($oInsertBefore, $oNode);

			return $this;
		}

		/// <summary>
		///  The parent node of the current node
		/// </summary>
		/// <returns type="bSimpleXml" />
		public function ParentNode() {

			$d =dom_import_simplexml($this);
			$oReturn =simplexml_import_dom($d->parentNode, get_class($this));

			return $oReturn;
		}

		/// <summary>
		///  Removes a child node from the current node
		/// </summary>
		/// <param type="bXml|bSimpleXml">
		///  the child node to remove
		/// </param>
		/// <returns type="bSimpleXml">
		///   The current node with the child removed
		/// </returns>
		public function RemoveChild(IbXml $xNode) {

			$oNode =dom_import_simplexml($xNode);

			$oParent =$oNode->parentNode;
			$oParent->removeChild($oNode);

			return $this;
		}

		/// <summary>
		///  Removes the current node and returns the parent
		/// </summary>
		/// <returns type="bSimpleXml">The parent node</returns>
		public function RemoveNode() {

			$oNode =dom_import_simplexml($this);

			$oParent =$oNode->parentNode;
			$oParent->removeChild($oNode);

			return simplexml_import_dom($oParent, get_class($this));
		}

		/// <summary>
		///   Swaps out the child node with the new node passed in
		/// <summary>
		/// <param type="bXml|bSimpleXml" name="mNodeOld">
		///   The child node to swap out
		/// </param>
		/// <param type="mixed" name="bNodeNew">
		///   Any Xml Type or stream to replace the child node with
		/// </param>
		/// <param type="bool" name="bWith" optional="true">
		///   a switch to tell if the child node is the current node, should
		///   really only be called from the <function name="ReplaceWith" /> function.
		/// </param>
		/// <returns type="bSimpleXml">
		///   The current node with the replaced child node in place
		/// </returns>
		private function _ReplaceChild(IbXml $xNodeOld, $mNodeNew, $bWith =false) {

			$xNodeNew =bXml::ToXml($mNodeNew);

			$oNodeOld =dom_import_simplexml($xNodeOld);
			$oNodeNew =dom_import_simplexml($xNodeNew);

			$oNode =dom_import_simplexml($this);
			$oDom =$oNode->ownerDocument;

			$oNodeReplacement =$oDom->importNode($oNodeNew, true);

			$oNode =($bWith ? $oNode->parentNode : $oNode);

			$oNode->replaceChild($oNodeReplacement, $oNodeOld);

			return $this;
		}

		// <summary>
		///   Swaps out the child node with the new node passed in
		/// <summary>
		/// <param type="bXml|bSimpleXml" name="mNodeOld">
		///   The child node to swap out
		/// </param>
		/// <param type="mixed" name="bNodeNew">
		///   Any Xml Type or stream to replace the child node with
		/// </param>
		/// <returns type="bSimpleXml">
		///   The current node with the replaced child node in place
		/// </returns>
		public function ReplaceChild(IbXml $xNodeOld, $mNodeNew) {
		
			return $this->_ReplaceChild($this, $mNode);
		}
		
		/// <summary>
		///   Swaps out the current node with the new node passed in
		/// <summary>
		/// <param type="mixed" name="mNode">
		///   The xml node (of any xml type or stream)
		/// </param>
		/// <returns type="bSimpleXml">
		///   The replaced node as the current node in the document
		/// </returns>
		public function ReplaceWith($mNode) {

			return $this->_ReplaceChild($this, $mNode, true);
		}

		/// <summary>
		///   Returns the root or document node
		/// </summary>
		/// <returns type="bSimpleXml" />
		public function RootNode() {

			return simplexml_import_dom(
				dom_import_simplexml($this)->ownerDocument,
				get_class($this)
			);
		}

		/// <summary>
		/// 	Pass in a stylesheet and it will be transformed aginst the full xml of this object
		/// 	NOTE: This returns a new bXml object (with only the DomDocument part filled). 
		/// 	The reason this is, is because sometimes it doesn't
		/// 	return valid XML, so if it returned a bSimpleXml object, it would be invalid. ie. you
		/// 	have an XSL stylesheet returning SQL query statements.
		/// 	Also NOTE: that there will be a $bXml:CurrentNode parameter automaticly passed, so you
		/// 	can select it for transformations. Bceuse the whole $this is passed to be transformed.
		/// </summary>
		/// <param type="mixed" name="mXsl">Any XML type can be passed in from a string to bXml</param>
		/// <param type="array" name="aArgument" optional="true">
		/// 	Need to pass in the form array(<namespace>, <key>, <value>). So this is an array of arrays
		/// </param>
		/// <returns type="bXml" />
		public function TransformWith($mXsl, array $aArgument =array()) {
			libxml_use_internal_errors(true);
			
			$aCurrentPath =array(array('', 'bXml:CurrentNode', $this->PathFinder($this, bXml::USE_PATH_POSITION)));
			$aArgument =array_merge($aCurrentPath, $aArgument); // will always pass the currentNode to the stylesheet
	
			$xXsl =new bXml($mXsl);
			// This is a funky way of creating a Namespace in DOM... can't just make an xmlsn:bXml attribute...
			$xXsl->AsDom()->documentElement
				->setAttributeNS('http://www.art-k-tec.com/code/bxml', 'bXml:transform_at', 'CurrentNode');
			
			$this->LoadStylesheet($xXsl, $aArgument);

			//$oDom =dom_import_simplexml($this->cloneNode()->parentNode())->ownerDocument;
			$oTransformed =$this->ObjectProcessor()->transformToDoc(dom_import_simplexml($this));

			$aXmlTransformationError = libxml_get_errors();
			if(!empty($aXmlTransformationError)) {
				
				foreach($aXmlTransformationError as $oError) $aMessage[] =$oError->message;
				
				throw new bXmlException(
					'TRANSFORMATION ERROR: '.implode('. ', $aMessage),
					bXmlException::INVALID_TRANSFORMATION
				);
			}
			
			$sObjectType =$this->ObjectType();
			return new $sObjectType($oTransformed);
		}

		/// <summary>
		///  The XML node to wrap around the current node, will append current node
		///  to the end of any children of the passed in node
		/// </summary>
		/// <param type="mixed" name="mNodeWrap">
		///   Any valid XML node type or stream
		/// </param>
		/// <returns type="bSimpleXml">The current node as WRAPED by the new node</returns>
		public function WrapWithNode($mNode) {
			$d =dom_import_simplexml($this);

			$oDom =$d->ownerDocument;
			$xNode =bXml::ToXml($mNode, true);
			$oWrap =dom_import_simplexml($xNode);
			$n =$oDom->importNode($oWrap, true);

			$d->parentNode->insertBefore($n);
			$n->appendChild($d);

			return $this;
		}

		/// <summary>
		/// 	returns the current node name, alias for <method name="getName" />
		/// </summary>
		public function NodeName(){
			return dom_import_simplexml($this)->nodeName;
		}

		/// <summary>
		///   Returns the type of the node currently being evaluated
		/// <summary>
		/// <returns type="int">Codes are based on the DOM Xml Type codes</returns>
		public function NodeType() {

			return dom_import_simplexml($this)->nodeType;
		}

		/// <summary>
		///  Set or get the value of the current node
		/// </summary>
		/// <param type="string" name="sValue" optional="true">
		///   The value to set the current node to. When no value is passed in,
		///    it returns the current value.
		/// </param>
		/// <returns type="string">The value of the node</returns>
		public function NodeValue($sValue =null) {

			if($sValue ==null) {

				return dom_import_simplexml($this)->nodeValue;
			} else {

				dom_import_simplexml($this)->nodeValue =$sValue;
				return $this;
			}
		}

		/// <summary>
		///   Returns the first node or false if the node is not valid
		///   of a child node.
		/// <summary>
		/// <returns type="bSimpleXml|false" />
		public function FirstChild() {

			$oNode =dom_import_simplexml($this)->firstChild;
			while(($oNode->nodeType ==XML_TEXT_NODE || $oNode->nodeType ==XML_CDATA_SECTION_NODE) && $oNode !=null) {

				$oNode =$oNode->nextSibling;
			}
			
			$oThis =simplexml_import_dom($oNode, get_class($this));
			
			return $oThis;
		}

		/// <summary>
		///   Returns the last node or false if the node is not valid
		///   of a child node.
		/// <summary>
		/// <returns type="bSimpleXml|false" />
		public function LastChild() {

			$oNode =dom_import_simplexml($this)->lastChild;
			while(($oNode->nodeType ==XML_TEXT_NODE || $oNode->nodeType ==XML_CDATA_SECTION_NODE) && $oNode !=null) {

				$oNode =$oNode->previousSibling;
			}
			
			$oThis =simplexml_import_dom($oNode, get_class($this));
			
			return $oThis;
		}

		/// <summary>
		///   Returns the preceeding sibling node or false if you are at the first node
		///   of a child node.
		/// <summary>
		/// <returns type="bSimpleXml|false" />
		public function PreviousSibling() {

			$oNode =dom_import_simplexml($this)->previousSibling;
			while($oNode->nodeType ==XML_TEXT_NODE || $oNode->nodeType ==XML_CDATA_SECTION_NODE || $oNode->nodeType ==XML_COMMENT_NODE) {

				$oNode = $oNode->previousSibling;
			}
			$oThis =simplexml_import_dom($oNode, get_class($this));

			return $oNode ==null ? $this : $oThis;
		}

		/// <summary>
		///   Returns the next sibling node or false if you are at the last node
		///   of a child node.
		/// <summary>
		/// <returns type="bSimpleXml|false" />
		public function NextSibling() {

			$oNode =dom_import_simplexml($this)->nextSibling;
			while($oNode->nodeType ==XML_TEXT_NODE || $oNode->nodeType ==XML_CDATA_SECTION_NODE || $oNode->nodeType ==XML_COMMENT_NODE) {

				$oNode = $oNode->nextSibling;
			}
			$oThis =simplexml_import_dom($oNode, get_class($this));

			return $oNode ==null ? $this : $oThis;
		}


		/// <summary>
		/// 	Returns either the node at the positon specified or gives the position of the current node
		/// </summary>
		/// <param type="integer" name="nPosition" optional="true">The position of the node to return</param>
		/// <returns type="bSimpleXml">The node at the postion specified</returns>
		/// <returns type="integer">The current node</returns>
		public function Position($nPosition =null) {

			if($nPosition ==null) {

				$n =0;
				$oNode =dom_import_simplexml($this);
				$sThisName =$oNode->nodeName;

				while(!!$oNode) {
					$sNodeName =$oNode->nodeName; // must be before, otherwise node might be null after previous siblin move
					$oNode =$oNode->previousSibling;

					if($sNodeName ==$sThisName) { $n++; }
				}

				$mReturn =$n;
			} else {

				$oNode = dom_import_simplexml($this)->parentNode->childNodes->item($nPosition -1);
				$mReturn =simplexml_import_dom($oNode, get_class($this));
			}

			return $mReturn;
		}

		/// <summary>
		///   Returns the namespace prefix of the node id available.
		/// </summary>
		public function Prefix() {

			return dom_import_simplexml($this)->prefix;
		}

		/// <summary>
		/// 	Generates an Xpath of the current location. Will walk backwards from any passed node.
		/// </summary>
		/// <param type="bSimpleXml" name="xNode" optional="true">The node to find the path from</param>
		/// <returns type="string">The XPath</returns>
		public function PathFinder(IbXml $xNode =null, $bFullPath =false) {

			$sPath ='';
			$oNode =dom_import_simplexml((!!$xNode ? $xNode : $this));

			while(!!$oNode && !($oNode instanceof DomDocument)) {
				$xNode =simplexml_import_dom($oNode);
				$aNamespace =$this->ObjectNamespace($xNode);

				$sPrefix =(($s =$oNode->namespaceURI) !='' ? $aNamespace[$s].':' : '');
				$sNodeName =$oNode->nodeName;
				$nPosition =count(
					(array)$xNode->xpath('preceding-sibling::*[name() = "'.$sNodeName.'"]')
				) +1;

				$sPath ='/'.$sPrefix.$sNodeName.($nPosition > 1 || $bFullPath ? '['.$nPosition.']' : '').$sPath;

				$oNode =$oNode->parentNode;
			}

			return new bPathFinder($sPath);
		}

		public function FormatOutput() {

			foreach(func_get_args() as $bBitwise) {

				$bFormat = isset($bFormat) ? $bFormat | $bBitwise : $bBitwise;
			}

			switch(true) {

				case $bFormat & bXml::PRETTY_PRINT:
				
					dom_import_simplexml($this)->ownerDocument->formatOutput = true;
				break;
			}
		}
		
		public function xpath($sXPath) {
			
			$this->ObjectNamespace();
			return parent::xpath($sXPath);
		}
		
		public function AsArray($mSimpleXml =null) {
			static $nStack =0;

			if($nStack ==0) { //forces it always to start from where the this is

				$mSimpleXml =$this;
				$sNodeName =$mSimpleXml->nodeName();
				
				if(count($mSimpleXml) > 1 || count($mSimpleXml->children()) == 0) {
				
					$aSimpleXml =(array)$mSimpleXml;
				} else {
				
					$aSimpleXml = array($sNodeName =>(array)$mSimpleXml);
				}
			} else {

				$aSimpleXml = (array)$mSimpleXml;
			}

			if($aSimpleXml ==array()) {

				$aSimpleXml = "";
			} else {

				if(isset($aSimpleXml[key($aSimpleXml)])) {

					$sSimpleXmlKey = key($aSimpleXml);
				 	if($sSimpleXmlKey =="@attributes" && is_array($aSimpleXml[$sSimpleXmlKey])) {

						if($sSimpleXmlKey =="@attributes") {

							$a =$aSimpleXml[$sSimpleXmlKey];
							foreach($a as $sKey=>$mValue){

								$aSimpleXml["@".$sKey] = $mValue;
							}

							unset($aSimpleXml[$sSimpleXmlKey]);
						}
					}
				}

				foreach($aSimpleXml as $sKey=>$mValue){

					if(!is_scalar($mValue)) {

						$nStack++;
							if(!is_array($mValue) && (string)$mValue !='') {
								if(dom_import_simplexml($mValue)->firstChild->nodeType ==XML_CDATA_SECTION_NODE)
								{
									$rValue = array("#cdata"=>(string)$mValue);
								}
								else
								{
									$rValue =(string)$mValue;
								}

								$aValue =$this->AsArray($mValue);
								if(empty($aValue)) {

									$aSimpleXml[$sKey] = $rValue;
								} else {

									$aSimpleXml[$sKey] = array($rValue, $this->AsArray($mValue));
								}
							} else {

								$aSimpleXml[$sKey ] = $this->AsArray($mValue);
							}
						$nStack--;
					}
				}
			}

			return $aSimpleXml;
		}

		public function AsHtml() {

			return dom_import_simplexml($this)->ownerDocument->saveHtml();
		}

		public function AsJson() {

			return json_encode($this->AsArray());
		}

		/// <summary>
		/// 	Returns as a dom document or dom element
		/// </summary>
		/// <param type="constant" name="cType" optional="true">
		/// 	Determine if you would want to return a Document or Element.
		/// </param>
		/// <returns type="DomDocument|DomElement" />
		public function AsDom($cType =bXml::DOM_DOCUMENT) {

			if($cType ==bXml::DOM_DOCUMENT) {

				return dom_import_simplexml($this)->ownerDocument;
			} else {

				return dom_import_simplexml($this);
			}
		}
	}

	/**
	 * <summary>
	 *  The XML class that is passed around the application. This is based off of simpleXML in that you
	 *  can do anything with it that you can with simplexml just some more things. Like you can add
	 *  child objects of XML and you can add CData and Comment nodes very easliy. It also handles some
	 *  counting and other internal XML conversion things (like ToArray() and Transform so you) can
	 *  pretty much use this in place of all the DOM, SimpleXml, Xpath and XSLTprocessor classes.
	 * </summary>
	 */
	class bXml extends ArrayObject implements IteratorAggregate, IbXml {
		private static $nMajor =1;
		private static $nMinor =0;
		private static $sRevision ="0000";

		private $xXml;
		private $oXmlDom;
		private $aRegisteredNamespaces;

		private $aXmlToArrayIndexMap;

		protected $sStatusMessage;
		protected $bSimpleXml ='bSimpleXml';
		protected $bXml ='bSimpleXml';
		
		const DOM_DOCUMENT		='(dom)[docu]{4}ment';
		const DOM_ELEMENT		='(dom)e.[(.|e)]ment';
		const NO_DECLARATION	='n(o)_[d]ecl(a)r$2tion';

		const ELEMENT_NODE 			=XML_ELEMENT_NODE;
		const TEXT_NODE 			=XML_TEXT_NODE;
		const CDATA_SECTION_NODE	=XML_CDATA_SECTION_NODE;
		const COMMENT_NODE			=XML_COMMENT_NODE;

		const AUTO_WRAP				=true;
		const USE_PATH_POSITION		=true;

		const PRETTY_PRINT			=1;

		/// <summary>
		///   returns the version of this class for resource management
		/// </summary>
		/// <returns type="string">The version</returns>
		public static function version() { return self::$nMajor.'.'.self::$nMinor.'.'.self::$sRevision; }

		/// <summary>
		/// 	Allows the input of any XML type and sets up the bSimpleXml Insides
		/// </summary>
		/// <param type="mixed" name="mXml">Any type of Xml</param>
		public function __construct($mXml =null, $bAutoWrap =false) {
			
			if(is_array($bAutoWrap)) {
				
				$this->aXmlToArrayIndexMap =$bAutoWrap;
				$bAutoWrap =false;
			}
			
			try {

				$this->xXml =$this->ToXml($mXml, $bAutoWrap, $this->bSimpleXml);
				if($this->xXml instanceof IbXml) {
					
					$this->xXml->_ObjectReference($this);
					$this->xXml->ObjectType(get_class($this));
				}
				
			} catch(Exception $e) {

				$this->sStatusMessage = $e->getMessage();
			}
		}

		/// <summary>
		///   Allows one to staticlly instanciate a new bXml object
		///   So the new keyword is not allowed
		/// </summary>
		/// <param type="mixed" name="mXml" optional="true">
		///    Any XML type as well as any stream that resolves as XML
		///    Note that when null is passed in a node of "<empty />" will be
		///    created automatically
		/// </param>
		/// <returns type="bXml">A NEW bXml object</returns>
		public static function Load($mXml =null, $bAutoWrap =false, $bSimpleXml ='bSimpleXml', $bXml ='bXml'){

			$oReturn =new $bXml($mXml, $bAutoWrap, $bSimpleXml);

			if(($sStatusMessage =$oReturn->_status_()) ===true) {

				return $oReturn;
			} else {

				throw new bXmlException(
					'A valid XML object was not created. Please check the input values. '.$sStatusMessage,
				bXmlException::INVALID_CONVERSION);
			}
		}

		public function _status_() {

			if($this->xXml ===false || $this->xXml ===null) {

				return $this->sStatusMessage;
			} else {

				return true;
			}
		}

		/// <summary>
		/// 	sets the iterarators over the internal bSimpleXml object
		/// </summary>
		/// <returns type="object">The iterators</returns>
		public function getIterator() {

			return new SimpleXMLIterator($this->xXml->AsXml());
		}

		/// <summary>
		/// 	Passes the bSimpleXml property requests along
		/// </summary>
		/// <params type="string" name="sChild">The name of the bSimpleXml node to lookup</params>
		/// <returns type="bSimpleXml" />
		public function __get($sChild) {

			return $this->xXml->{$sChild};
		}

		/// <summary>
		/// 	Passes the bSimpleXml property requests along and sets it
		/// </summary>
		/// <params type="string" name="sChild">The name of the bSimpleXml node to set</params>
		/// <params type="string" name="sData">The value to set to</params>
		/// <returns type="bSimpleXml" />
		public function __set($sChild, $sData) {

			return $this->xXml->{$sChild} =$sData;
		}

		/// <summary>
		/// 	Passes the bSimpleXml method requests along
		/// </summary>
		/// <params type="string" name="sMethod">The name of the bSimpleXml method to use</params>
		/// <params type="array" name="sMethod">The arguments of the bSimpleXml object</params>
		/// <returns type="bSimpleXml" />
		public function __call($sMethod, $a) {

			if(!($this->xXml instanceof bSimpleXml) && !($this->oXmlDom instanceof DOMDocument)) {
				
				throw new bXmlException(
					$this->_status_().' Both objects are NULL for the calling.', 
					bXmlException::INVALID_OBJECTS
				);
			}
			// if there is no bSimpleXml and there is a DomObject... then use
			// the DomObject as it there becuase a transformation was performed, without a valid
			// XML object being produced.
			$oObject =($this->xXml ==null && $this->oXmlDom !=null) ? $oObject =$this->oXmlDom : $oObject =$this->xXml;

			// we need to manually marshall the asXml and asHtml methods... there 
			// currently is no way to extend DomDoument --or rather get an extended object
			// back... so here we go.
			if($oObject instanceof DomDocument) {
				
				switch(strtolower($sMethod)) {
					
					case 'asxml':
						$sMethod ='SaveXml';
					break;
					case 'ashtml':
						$sMethod ='SaveHtml';
					break;
				}
			}
			return call_user_func_array(array($oObject, $sMethod), $a);
		}
		
		/// <summary>
		///  Passes through the isset function, to the first level of the bSimpleXML
		/// </summary>
		/// <param type="string" name="sString">
		///    The node you are looking to see if is set
		/// </param>
		/// <returns type="bool">Whether the node is set or not</returns>
		public function __isset($sString)
		{
			return isset($this->xXml->{$sString});
		}
		
		/// <summary>
		///  Passes through the unset function, to the first level of the bSimpleXML
		/// </summary>
		/// <param type="string" name="sString">
		///    The node you are looking to unset
		/// </param>
		public function __unset($sString)
		{
			unset($this->xXml->{$sString});
		}
		
		/// <summary>
		///  Allows access to the attributes of the bXml class
		/// </summary>
		/// <param type="string|int" name="mIndex">
		///    The index or the name of the attibute one is looking up
		/// </param>
		/// <returns type="string">The value of the attribute</returns>
		public function offsetGet($mIndex) {
		
			return $this->xXml[$mIndex];
		}

		/// <summary>
		///   Allows access to the attributes of the bXml class
		/// </summary>
		/// <param type="string|int" name="mIndex">
		///   The attribute name or index to change or set
		/// </param>
		/// <param type="string|int" name="mData">
		///   The value of the attribute is going to be set to.
		/// </param>
		public function offsetSet($mIndex, $mData) {

			return $this->xXml[$mIndex] =$mData;
		}

		/// <summary>
		///  Passes through the isset function, to the first level of the bSimpleXML attribute
		/// </summary>
		/// <param type="string" name="sString">
		///    The node you are looking to see if is set
		/// </param>
		/// <returns type="bool">Whether the node is set or not</returns>
		public function offsetExists($sString) {
			
			return isset($this->xXml[$sString]);
		}
		
		/// <summary>
		///  Passes through the unset function, to the first level of the bSimpleXML attribute
		/// </summary>
		/// <param type="string" name="sString">
		///    The node you are looking to unset
		/// </param>
		public function offsetUnset($sString) {
			
			unset($this->xXml[$sString]);
		}
		
		public function GetRegisteredNamespaces() {
			
			return $this->aRegisteredNamespaces;
		}
		
		public function registerXPathNamespace($sPrefix, $sNamespace) {
			
			$this->aRegisteredNamespaces[$sPrefix] =$sNamespace;
			return $this->xXml->registerXPathNamespace($sPrefix, $sNamespace);
		}
		
		public function AsArray() {

			return $this->xXml->asArray('!');
		}

		///<summary>
		///  This overrides the simpleXml AsHtml becuase there is the
		///  possiblity of a DOMDocument will Malformed XML ... but valid
		///  XSLT transformation output. So if that's the case then use
		///  use DOM otherwise use SimpleXML
		///</summary>
		public function AsHtml() {

			if($this->oXmlDom !==null) {

				return $this->oXmlDom->saveHtml();
			} else {

				return $this->xXml->AsHtml();
			}
		}
		
		///<summary>
		///  This overrides the bSimpleXml WrapNode becuase there is
		///  a problem trying to Add a root element to the DOM, so
		///  we'll just by-pass that and add the data here... it's
		///  not as elegent, but it is cleaner.
		///</summary>
		public function WrapWithNode($sNode) {
			
			$xXml =$this->ToXml($sNode);
			$xXml->AddChild((string)$this);
			
			$this->xXml =$xXml;
			return $this->xXml;
		}

		/// <summary>
		/// 	Takes any xml type pr resovlable stream and converts to bSimpleXml type
		/// </summary>
		/// <param type="mixed" name="mXml">
		///    The XML or stream
		///    <example> A stream example is: file:///XmlFileHere.xml </example>
		/// </param>
		public function ToXml($mXml =null, $bAutoWrap =false, $bSimpleXml ='bSimpleXml') {
			libxml_use_internal_errors(true);
			
			if($this && $this->bSimpleXml) { $bSimpleXml =$this->bSimpleXml; }

			if(is_string($mXml)) {

				if(!preg_match('/(<\/|\/>)/', $mXml)) {

					$aStreams =stream_get_wrappers();
					array_walk($aStreams, create_function('&$s', '$s =$s."://";'));

					preg_match('/([A-Z]+:\/\/)/i', $mXml, $aMatch);
					$sMatch =(empty($aMatch) ? '' : $aMatch[1]);

					if(file_exists($mXml) || in_array($sMatch, $aStreams)) {

						$mXml =file_get_contents($mXml);
					} else {

						if($bAutoWrap) {

							$mXml ='<xml>'.$mXml.'</xml>'; //auto wrapping.
						} else {

							throw new bXmlException (
								'A valid XML type has not been passed. Type of [string: '.$mXml.'] has been passed. ',
								bXmlException::INVALID_CONVERSION
							);
						}
					}
				}
			}

			switch(true){

				case $mXml ==null:

					if($bAutoWrap) {

						$oXml =eval("return new $bSimpleXml('<empty />')");
					} else {

						throw new bXmlException(
							'A valid XML type has not been passed. Type of [null] has been passed. ',
						bXmlException::INVALID_CONVERSION);
					}
				break;
				case($mXml instanceof $bSimpleXml):

					$oXml =$mXml;
				break;

				case($mXml instanceof SimpleXmlElement):
					$oXml =@simplexml_import_dom(dom_import_simplexml($mXml), $bSimpleXml);
				break;

				case($mXml instanceof DOMDocument):
				
					$this->oXmlDom = $mXml;
					$oXml =@simplexml_import_dom($mXml, $bSimpleXml);
				break;
				case($mXml instanceof DOMElement):

					$this->oXmlDom = $mXml->ownerDocument;
					$oXml =@simplexml_import_dom($mXml, $bSimpleXml);
				break;

				case(is_string($mXml)):
					$oXml =@simplexml_load_string($mXml, $bSimpleXml);

					if($oXml ===false && strpos($mXml, '<') !==false) {

						$aError =array();
						foreach(libxml_get_errors() as $oError) $aError[] =$oError->message;
						
						throw new bXmlException(
							implode('. ', $aError).
							'The input could not be converted to an bXml object. Asumed input was XML. ',
						bXmlException::INVALID_CONVERSION_STRING);
					} elseif($oXml ===false && strpos($mXml, '<') ===false) {

						$aError =array();
						foreach(libxml_get_errors() as $oError) $aError[] =$oError->message;
						
						throw new bXmlException(
							implode('. ', $aError).
							'The input could not be converted to an bXml object. Asumed input was a Qname. ',
						bXmlException::INVALID_CONVERSION);
					}
				break;

				case(is_array($mXml)):
		
					$mXml = $this->ArrayToXml($mXml);
					
				case($mXml instanceof bXml): //drop through
				
					$oXml =@simplexml_load_string($mXml->AsXml(), $bSimpleXml);
				break;

				case($mXml instanceof XmlWriter):
					$oXml =@simplexml_load_string($mXml->outputMemory(), $bSimpleXml);
					if($oXml ===false) {

						$aError =array();
						foreach(libxml_get_errors() as $oError) $aError[] =$oError->message;
						
						throw new bXmlException(
							implode('. ', $aError).
							'The input could not be converted to an '.$bSimpleXml.' object. Asumed XmlWriter was valid. ',
						bXmlException::INVALID_CONVERSION);
					}
				break;
				
				case (class_exists("bArray", false) && $mXml instanceof bArray): // HARDCODED TIE-IN TO bArray
				
					$oXml =@simplexml_load_string($mXml->AsXml(), $bSimpleXml);
				break;
				
				default:
				
					$aError =array();
					foreach(libxml_get_errors() as $oError) $aError[] =$oError->message;
					
					throw new bXmlException(
						implode('. ', $aError).
						'A valid XML type has not been passed. Type of ['.gettype($mXml).'] has been passed. ',
					bXmlException::INVALID_CONVERSION);
			}
			
			return $oXml;
		}

		/// <summary>
		/// 	Returns the array as a well-formed XML object - This is a port from
		/// 	the bArray implenetation. So there is no dependancy
		/// </summary>
		/// <returns type="string">The passed in array as a Xml</returns>
		private function ArrayToXml($oArray, $bSimpleXml ='bSimpleXml') {
			if($this && $this->bSimpleXml) { $bSimpleXml =$this->bSimpleXml; }
			
			static $nStack =0;
			static $oWriter =null;

			$sFunction =__FUNCTION__;

			if($nStack ==0) {
				$sClass = __CLASS__;
//				$aIndexMap =$this->aXmlToArrayIndexMap;
				
				$oWriter =new XmlWriter();
				$oWriter->openMemory();
			}

			$bIsNumericKey =false;
			
			foreach($oArray as $sKey =>$mValue) {

				// gets all of the attributes first, because once a node is written in, then writing
				// an attribute won't work! So we need to get all of those first.
				$aAttributes =array_flip(array_filter((array)$oArray, array($this, "FilterArrays")));
				$aFlippedKeys =array_filter($aAttributes, array($this, "FilterAttributes"));

				foreach($aFlippedKeys as $sAttributeValue =>$sAttributeKey) {

					$oWriter->writeAttribute(substr($sAttributeKey, 1), $sAttributeValue);
				}

				// We should have already gotten all of the attributes so we just need to skip past
				// attributes now.
				if($sKey[0] =="@") {
					continue;
				}
				
				if((string)$sKey ==(string)"#text") {
					
					$oWriter->text($mValue);
					continue;
				}
				
				if((string)$sKey ==(string)"#cdata") {
					
					$oWriter->writeCData($mValue);
					continue;
				}
				
				if(!$this->CheckQName($sKey)) { 
					
					
					if(is_numeric($sKey)) {

						$nNumeric =$sKey;
						$sKey = empty($this->aXmlToArrayIndexMap) ? 'n' : array_shift($this->aXmlToArrayIndexMap);
						$bIsNumericKey =true;
					} else {
					
						throw new bXmlException(
							"The QName $sKey is not a valid node name.",
							bXmlException::INVALID_NODE_NAME
						); 
					}
				}

				$oWriter->startElement($sKey);
				
				if($bIsNumericKey){
					$oWriter->writeAttribute('key', $nNumeric);
				}
				
				if(is_scalar($mValue)) {

					$oWriter->text($mValue);
				} elseif(is_array($mValue)) {

					$nStack++;
					$this->$sFunction($mValue);
					$nStack--;
				}

				$oWriter->endElement();
				
				if($bIsNumericKey) {
					array_unshift($this->aXmlToArrayIndexMap, $sKey);
					$bIsNumericKey =false;
				}
			}

			if($nStack ==0) {
				
//				$this->aXmlToArrayIndexMap =$aIndexMap;
				return @simplexml_load_string($oWriter->outputMemory(), $bSimpleXml);
			}
		}

		protected function FilterArrays($mValue) {

			return is_scalar($mValue) && !is_bool($mValue);
		}

		protected function FilterAttributes($sKey) {

			return ($sKey[0] =="@");
		}

		protected function CheckQName($sName) {

			return (bool)preg_match('/([A-Za-z_])([A-Za-z0-9\._-]*)/', $sName);
		}
		
		public function __toString() {

			return $this->asXml();
		}
	}
?>